package com.example.demo.aspect;


import org.apache.commons.lang3.time.StopWatch;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

/**
 * 简单的作结：
 * 1.不要在基类上做横切关注点， 要关注到具体的实现类上进行，好像基类的横切关注点的写法与具体实现类的不一样。 baseService+.run().要待加号？  关于这点应该是有办法的，未测试（感觉没什么意思）。
 * 2.如果在1的前提下(在子类上做横切关注点)， 在基类中做实现，而不再在子类中覆盖此方法，不能被拦截。 testservice2impl 中如果不重写 run方法，即使 其基类中存在run方法，run方法不能被拦截
 * 3.内部嵌套调用无效
 * 4.多用接口，应该多基于接口的切面代理，而不是基于基类
 *
 */
@Aspect
@Component
public class TimeInterceptor {

    Logger logger = LoggerFactory.getLogger(TimeInterceptor.class);

    //ok
    public static final String POINT_SDK = "execution(* com.example.demo.service.*.*(..))";

    //ok
    public static final String POINT_SDK_1 = "execution(* com.example.demo.service1.impl.TestService3Impl.*(..))";

    // fail  baseService.test();ok   run() fail 当run方法在TestService2Impl中重写时，能够被拦截
    public static final String POINT_SDK_2 = "execution(* com.example.demo.service1.impl.TestService2Impl.*(..))";

    // fail   和 sdk_5一样没有被调用
    public static final String POINT_SDK_3 = "execution(* com.example.demo.service1.impl.TestService4Impl.test(..))";

    //ok
    public static final String POINT_SDK_4 = "execution(* com.example.demo.service1.impl.TestService4Impl.run(..))";

    //fail demoApplication 中调用的是 testService5.handle(); 当然拦截不到.
    public static final String POINT_SDK_5 = "execution(* com.example.demo.service1.impl.TestService5Impl.test(..))";

    //ok
    public static final String POINT_SDK_6 = "execution(* com.example.demo.service1.impl.TestService5Impl.handle(..))";

    // ok
    public static final String POINT_SDK_7 = "execution(* com.example.demo.service1.impl.TestService6Impl.handle(..))";

    //ok
    public static final String POINT_SDK_8 = "execution(* com.example.demo.service1.impl.TestService6Impl.handle(..))";

    //fail 不行，说明只能使用接口，不要使用父类。如果非要使用父类做 横切关注点， 需要将 调用 父类的方法写到接口中去. 同一个方法不能支持嵌套调用
    public static final String POINT_SDK_9 = "execution(* com.example.demo.service1.impl.TestService7Impl.test(..))";

    @Around(POINT_SDK_2)
    public Object timeAround(ProceedingJoinPoint joinPoint) throws Throwable {
        Object[] args = joinPoint.getArgs();

        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        String methodName = signature.getDeclaringTypeName() + "." + signature.getName();

        StopWatch clock = new StopWatch();
        clock.start();

        try {
            Object obj = joinPoint.proceed(args);
            return obj;
        } finally {
            clock.stop();
            logger.info("method({}) cost({})", methodName, clock.getTime());
        }
    }
}
